# $Id: ConfigCopy.pm 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
# Copyright (c) 2008 Prabhu Gurumurthy <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
package ConfigCopy;

require 5.004;

use Carp 'croak', 'carp';
use strict;
use warnings;
use File::Temp qw(tempfile);
use File::Copy;
use Net::SNMP;
use Socket;
use lib ".";
use snmp;
use vars qw($VERSION);

$VERSION = 1.1;

# My new function
sub ConfigCopy::new {
   my ($class) = @_;
   my $self = {
      _lastChange => undef,
      _configFile => undef,
      _tftpServer => undef,
      _snmpSession => undef,
      _sysDescr => undef,
      _session => undef,
      _error => undef,
      _sshUser => undef,
      _sshPass => undef,
      _sshPort => undef,
      _sshEnable => undef,
      _sshScript => undef,
      _router => undef,
   };

   bless ($self, $class);
   return $self;
}

# This is where the caller get the error string
sub ConfigCopy::get_error($)
{
   my ($self) = @_;
   return $self->{_error};
}

# Close the SNMP 
sub ConfigCopy::close ($)
{
   my ($self) = @_;

   if (defined($self->{_snmpSession})) {
      $self->{_snmpSession}->close();
   }

   while (my($key, $value) = each(%$self)) {
      $self->{$key} = undef;
   }

}

# Set the SNMPv2 parameters, we do not support v1 and v3 yet!
sub ConfigCopy::set_params ($$$$$$)
{
   my (($self), $router, $community, $cfg_file, $tftp, $sleep) = @_;

   # Tell me what is the tftp server
   if (defined($tftp)) {
      $self->{_tftpServer} = $tftp;
   }
   else {
      $self->{_tftpServer} = "127.0.0.1";
   }

   # What is the sleep time?, if undefined goes to else loop 
   if (defined($sleep)) {
      $self->{_sleepTime} = $sleep;
   }
   else {
      $self->{_sleepTime} = 30;
   }

   # Set the router, this is used for ssh_connect function
   if (defined($router)) {
      $self->{_router} = $router;
   }

   my $retval = 0;

   # instantiate the snmp session handle.
   $self->{_snmpSession} = new snmp;
   my $result = $self->{_snmpSession}->set_v2(2, $router, $community);
   if ($result == 0) {
      if ($self->{_snmpSession}->connect() == 1) {
         $self->{_error} = $self->{_snmpSession}->get_error();
         $retval = 1;
      }
      else {
         $self->{_session} = $self->{_snmpSession}->get_session();
      }
   }
   else {
      $self->{_error} = $self->{_snmpSession}->get_error();
      $retval = 1;
   }

   if ($retval == 0) {
      # If the configuration file is defined, then I will take it
      # The configuration file has to be in /tftpboot, as all our config
      # copies are based of TFTP, the default tftp directory is /tftpboot.
      # Else, I create a temp file in /tftpboot and call it based on the
      # hostname of the device which is to be backed up.
      if (defined($cfg_file)) {
         $self->{_configFile} = $cfg_file;
      }
      else {
         my $dir = "/tftpboot";
         my $temp_file = sprintf("%s/%s.cfg", $dir, $router);
         my ($tmp_fh, $tmp_file) = tempfile(DIR => $dir);
         if (-f $tmp_file) {
            move ($tmp_file, $temp_file); 
            chmod(0666, $temp_file);
            $self->{_configFile} = $temp_file; 
         }
         else {
            $self->{_error} = "cannot create target config file";
            $retval = 1;
         }
      }
   }

   # I try to the system description, if I cannot get system description,
   # then obviously an error has occured in simple snmpget
   if ($retval == 0) {
      $self->{_sysDescr} = $self->{_snmpSession}->get_sys_descr();
      if (!defined($self->{_sysDescr})) {
         $self->{_error} = $self->{_snmpSession}->get_error();
         $retval = 1;
      }
   }

   return $retval;
}

# SSH connect function is used to set the SSH parameters for calling the 
# python script later.
sub ConfigCopy::ssh_connect ($$$$$$)
{
   my (($self), $script, $user, $passwd, $port) = @_;

   # If port not defined or if port is zero, I assume the port to be 22
   # default ssh port
   if (!defined($port) or ($port == 0)) {
      $port = 22;
   }

   # Set other parameters, the error is thrown only in the get_config 
   # function
   if (defined($script)) {
      $self->{_sshScript} = $script;
   }

   if (defined($user)) {
      $self->{_sshUser} = $user;
   }

   if (defined($passwd)) {
      $self->{_sshPass} = $passwd;
   }

   $self->{_sshPort} = $port;
}

# Set config file is a utility function, rarely used, if used, then the file
# must reside within the tftp root directory, there is no easy way for the
# script to the tftp root directory, as it can be a separate daemon, without
# any ctl functionality
sub ConfigCopy::set_config_file ($$)
{
   my (($self), $config_file) = @_;

   if (defined($config_file)) {
      $self->{_configFile} = $config_file;
   }
}

# Called by the caller to finally get the configuration file for doing some
# post-op work, such as moving it, or getting digest values and such.
sub ConfigCopy::get_config_file ($)
{
   my ($self) = @_;

   return $self->{_configFile};
}

# Get what sort of device it is, not standard, just useful utility function,
# does not do anything major or critical.
sub ConfigCopy::get_device ($)
{
   my ($self) = @_;
   my $retval = undef;

   if ($self->{_sysDescr} =~ /Cisco IOS/) {
      $retval = "cisco";
   }
   elsif ($self->{_sysDescr} =~ /Ethernet/) {
      $retval = "dell";
   }
   elsif ($self->{_sysDescr} =~ /Alteon/) {
      $retval = "alteon";
   }
   elsif ($self->{_sysDescr} =~ /Adaptive Security Appliance/) {
      $retval = "asa";
   }
   elsif ($self->{_sysDescr} =~ /PIX Security Appliance/) {
      $retval = "pix";
   }

   return $retval;
}

# Core of this module.
sub ConfigCopy::get_config ($)
{
   my ($self) = @_;
   my $retval = 0;

   # Is session defined?
   if (defined($self->{_session})) {
      my @list = ();
      my $result = undef;
      push my @temp, split(/\//, $self->{_configFile});
      my $file = $temp[$#temp];

      # If my device is IOS
      if ($self->{_sysDescr} =~ /IOS/) {
         if ($self->{_sysDescr} =~ /CE500/) {
            # This is for Cisco Catalyst Express 500 PoE switch
            # Thanks to Solarwinds for getting this done!.
            my $copy_info = sprintf(
               "1.3.6.1.4.1.9.2.1.55.%s", $self->{_tftpServer});
            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_info, OCTET_STRING, $file]);
         }
         else {
            # Backup the IOS based on following website:
            # http://www.cisco.com/en/US/tech/tk648/tk362/technologies_configuration_example09186a0080094aa6.shtml
            # These MIBs are used: 
            # CISCO-SMI-V1SMI.my 
            # SNMPv2-TC-V1SMI.my 
            # CISCO-CONFIG-COPY-MIB-V1SMI.my 
            # CISCO-FLASH-MIB.my 
            # ccCopyEntryRowStatus 
            #     TYPE : integer 
            #        VALUES : createAndGo(4) : Create an entry 
            #                 destroy(6) : Delete an entry 
            #
            # ccCopyProtocol 
            #     TYPE : integer 
            #        VALUES : tftp(1) : To use tftp as protocol to copy
            #                 rcp(3) : To use RCP as protocol to copy 
            # 
            # ccCopySourceFileType: specifies the type of file to copy from. 
            #     TYPE : integer 
            #        VALUES : networkFile(1) 
            #                 startupConfig(3) 
            #                 runningConfig(4)
            #
            # ccCopyDestFileType: specifies the type of file to copy to. 
            #     TYPE : integer 
            #        VALUES : networkFile(1) 
            #                 startupConfig(3) 
            #                 runningConfig(4)
            #
            # ccCopyServerAddress: The IP address of the (TFTP) server from 
            #                      (or to) which to copy the configuration 
            #                      file. 
            #     TYPE : ipaddress 
            #        VALUES : Any valid ip address xxx.xxx.xxx.xxx 
            #
            # ccCopyFileName 
            #     TYPE : octetstring 
            #        VALUES : The file name (including the path, if applicable)
            #                 of the file. The file name is referred to as 
            #                 <filename>. 
            # 
            # Example for cmd line:
            # snmpset -v 1 -c private <device name> 
            # .1.3.6.1.4.1.9.9.96.1.1.1.1.2.<Random number> integer 1 
            # .1.3.6.1.4.1.9.9.96.1.1.1.1.3.<Random number> integer 4 
            # .1.3.6.1.4.1.9.9.96.1.1.1.1.4.<Random number> integer 1 
            # .1.3.6.1.4.1.9.9.96.1.1.1.1.5.<Random number> ipaddress 
            #                             "<server ip address>" 
            # .1.3.6.1.4.1.9.9.96.1.1.1.1.6.<Random number> octetstring 
            #                             "<file name>" 
            # .1.3.6.1.4.1.9.9.96.1.1.1.1.14.<Random number> integer 4 

            my $rand_number = "1";
            my $copy_protocol = sprintf(
               "1.3.6.1.4.1.9.9.96.1.1.1.1.2.%d", $rand_number);
            my $copy_src_file_type = sprintf(
               "1.3.6.1.4.1.9.9.96.1.1.1.1.3.%d", $rand_number);
            my $copy_dest_file_type = sprintf(
               "1.3.6.1.4.1.9.9.96.1.1.1.1.4.%d", $rand_number);
            my $copy_server_addr = sprintf(
               "1.3.6.1.4.1.9.9.96.1.1.1.1.5.%d", $rand_number);
            my $copy_file_name = sprintf(
               "1.3.6.1.4.1.9.9.96.1.1.1.1.6.%d", $rand_number);
            my $copy_entry_status = sprintf(
               "1.3.6.1.4.1.9.9.96.1.1.1.1.14.%d", $rand_number);

            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_entry_status, INTEGER, 6]);
            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_protocol, INTEGER, 1]);
            $result = $self->{_session}->set_request(
               -varbindlist => [
               $copy_server_addr, IPADDRESS, $self->{_tftpServer}]);
            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_file_name, OCTET_STRING, $file]);
            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_src_file_type, INTEGER, 4]);
            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_dest_file_type, INTEGER, 1]);
            $result = $self->{_session}->set_request(
               -varbindlist => [$copy_entry_status, INTEGER, 1]);

            if (!defined($result)) {
               unlink($self->{_configFile});
               $retval = 1;
            }
         }
      }
      elsif ($self->{_sysDescr} =~ /Ethernet/) {
         # This for Dell, but do not know whether this works still.
         push @list, ("1.3.6.1.4.1.89.87.2.1.7.1", INTEGER, 2);
         push @list, ("1.3.6.1.4.1.89.87.2.1.8.1", INTEGER, 3);
         push @list, ("1.3.6.1.4.1.89.87.2.1.9.1", IPADDRESS,
            $self->{_tftpServer});
         push @list, ("1.3.6.1.4.1.89.87.2.1.11.1", OCTET_STRING, $file);
         push @list, ("1.3.6.1.4.1.89.87.2.1.17.1", INTEGER, 4);

         $result = $self->{_session}->set_request(-varbindlist => [@list]);
         if (!defined($result)) {
            unlink($self->{_configFile});
            $retval = 1;
         }
      }
      elsif ($self->{_sysDescr} =~ /Alteon/) {
         # This is for Alteon, but do not know whether this works still.
         push @list, ("1.3.6.1.4.1.1872.2.1.2.1.18.0", IPADDRESS,
            $self->{_tftpServer});
         push @list, ("1.3.6.1.4.1.1872.2.1.2.1.19.0", OCTET_STRING, $file);
         push @list, ("1.3.6.1.4.1.1872.2.1.2.1.21.0", INTEGER, 4);

         $result = $self->{_session}->set_request(-varbindlist => [@list]);
         if (!defined($result)) {
            unlink($self->{_configFile});
            $retval = 1;
         }
      }
      elsif (($self->{_sysDescr} =~ /Adaptive Security Appliance/) or
         ($self->{_sysDescr} =~ /PIX Security Appliance/)) {

         # Copy ASA or PIX
         push my @file, split(/\//, $self->{_configFile});
         my $config = $file[$#temp];

         # Call the python script (typically asabackup.py located in
         # /usr/ssn/bin/)
         my $sshScript = sprintf("%s -H %s -t %s -l %s -P %s -f %s -p %d", 
            $self->{_sshScript}, $self->{_router}, $self->{_tftpServer}, 
            $self->{_sshUser}, $self->{_sshPass}, $config, $self->{_sshPort});
         $retval = system($sshScript);
         if ($retval == 1) {
            $self->{_error} = sprintf(
               "backup using python script faile for %s", $self->{_router});
            unlink($self->{_configFile});
         }
      }
      else {
         $self->{_error} = sprintf("unknown device %s", $self->{_sysDescr});
         $retval = 1;
      }

      sleep($self->{_sleepTime});
   }
   else {
      $retval = 1;
   }

   return $retval;
}

# I am a nice nice module
1;
